<?php

require_once 'HtmlBinder.php';
require_once 'IModelBinder.php';
require_once 'PropertyMetaData.php';
require_once 'ValidationResult.php';
require_once 'attributes/AttributeParser.php';
require_once 'attributes/FilterAttributes.php';
require_once 'attributes/DecorationAttributes.php';
require_once 'attributes/ValidationAttributes.php';

class DefaultModelBinder implements IModelBinder
{
    /**
     *
     * @var array
     */
    private $bindings = array();

    /**
     *
     * @var boolean
     */
    private $modelBound = false;

    /**
     *
     * @param object $model
     */
    public function bindModel($model)
    {
        if (!is_object($model)) {
            throw new Exception("The model must be an object.");
        }
        
        $this->resolveBindings($model, null);

        if (!$this->modelBound) {
            $this->bindRequestData();
        }

        HtmlBinder::initialize($this->bindings);

        $this->modelBound = true;
    }

    /**
     *
     * @param object|array $model
     * @param string $path
     */
    private function resolveBindings($model, $path, $arrayKey = null)
    {
        if (is_object($model)) {
            foreach (get_object_vars($model) as $propertyName => $value) {
                if ($path == null) {
                    $propertyPath = $propertyName;
                } else if ($arrayKey !== null) {
                    $propertyPath = $path . '-' . $arrayKey . '-' . $propertyName;
                    while ($arrayKey !== null && isset($this->bindings[$propertyPath])) {
                        $propertyPath = $path . '-' . ++$arrayKey . '-' . $propertyName;
                    }
                } else {
                    $propertyPath = $path . '-' . $propertyName;
                }
                if (is_object($value) || is_array($value)) {
                    $this->resolveBindings($value, $propertyPath);
                } else {
                    $attributes = AttributeParser::getPropertyAttributesFor($model, $propertyName);
                    $this->bindings[$propertyPath] = new PropertyMetaData($model, $propertyName, $propertyPath, $attributes);
                }
            }
        } else if (is_array($model)) {
            $i = 0;
            foreach ($model as $value) {
                $this->resolveBindings($value, $path, $i++);
            }
        }
    }

    /**
     *
     * @param string $name
     * @return PropertyMetaData
     */
    public function getPropertyMetaData($name)
    {
        return isset($this->bindings[$name]) ? $this->bindings[$name] : null;
    }

    /**
     *
     * @return boolean
     */
    public function isModelValid()
    {
        foreach ($this->bindings as $propertyMetaData)
        {
            if ($propertyMetaData->getValidationResult() != null
                && !$propertyMetaData->getValidationResult()->isValid()) {
                    return false;
            }
        }
        return true;
    }

    /**
     *
     */
    private function bindRequestData()
    {
        foreach ($this->bindings as $name => $propertyMetaData) {
            if (!isset($_POST[$name])) {
                continue;
            }

            $postedValue = ($_POST[$name] === '') ? null : $_POST[$name];
            $filteredValue = $this->filterPropertyValue($propertyMetaData, $postedValue);

            $this->validateProperty($propertyMetaData, $filteredValue);
            
            $propertyName = $propertyMetaData->getPropertyName();
            $container = $propertyMetaData->getContainer();

            $container->$propertyName = $filteredValue;

        }
    }

    /**
     *
     * @param PropertyMetaData $propertyMetaData
     * @param mixed $value
     * @return mixed
     */
    protected function filterPropertyValue(PropertyMetaData $propertyMetaData, $value)
    {
        $attributes = $propertyMetaData->getAttributes();

        if ($attributes != null) {
            foreach ($attributes as $attribute) {
                if (!$attribute instanceof IFilterAttribute) {
                    continue;
                }
                $value = $attribute->filter($value);
            }
        }
        return $value;
    }

    /**
     *
     * @param PropertyMetaData $propertyMetaData
     * @param mixed $value
     */
    protected function validateProperty(PropertyMetaData $propertyMetaData, $value)
    {
        $attributes = $propertyMetaData->getAttributes();

        $valid = true;

        $validationMessages = array();
        if ($attributes != null) {
            foreach ($attributes as $attribute) {
                if (!$attribute instanceof IValidationAttribute) {
                    continue;
                }

                $result = $attribute->isValid($value);
                if (!$result) {
                    $validationMessages[] = $attribute->getErrorMessage();
                }

                if ($valid) {
                    $valid = $result;
                }
            }
        }
        $propertyMetaData->setValidationResult(new ValidationResult($valid, $validationMessages));
    }
}